<!--
 * 标签
 * @作者: 陈老师儿~
 * @手机: 18560000860
 -->
<template>
    <view class="pure-tabs pure-hide-scrollbar" :class="classes" :style="styles">
        <scroll-view
            class="pure-tabs-scroll-view"
            :scroll-x="canScrollX"
            :scroll-y="canScrollY"
            :scroll-left="scrollLeft"
            :scroll-top="scrollTop"
            :scroll-with-animation="animation"
            :id="scrollViewId"
            :scroll-into-view="scrollIntoView"
            :show-scrollbar="false"
        >
            <view class="pure-tabs-list">
                <view class="pure-tabs-items" :id="containerId">
                    <!-- items -->
                    <slot></slot>

                    <!-- 指示条 -->
                    <view class="pure-tabs-indicator" v-if="!hideIndicator" @animationend="handleIndicatorAnimationEnd"> </view>
                </view>
            </view>
        </scroll-view>
    </view>
</template>

<script>
    import { computed } from "vue";
    import props from "./props.js";
    export default {
        options: {
            // 将自定义节点设置成虚拟的
            // 头条、飞书：因为合并后丢失事件和 'provide/inject'
            // 京东：因为合并后不能合并外层 'class'
            // 百度：因为合并后会在控制台疯狂报警告
            // #ifndef MP-TOUTIAO || MP-LARK || MP-JD || MP-BAIDU
            virtualHost: true,
            // #endif

            // 组件样式隔离
            styleIsolation: "apply-shared",

            // 允许使用多个插槽
            multipleSlots: true,
        },
        props,
        emits: ["tabClick"],
        data() {
            return {
                // IDs
                scrollViewId: "", // 滚动容器的ID
                containerId: "", // 容器ID
                scrollIntoView: "", // 绑定的 scroll-into-view 属性

                // 滚动值
                scrollLeft: 0,
                scrollTop: 0,

                // 用于存储 items 相关数据, key 为元素节点 id
                items: {},

                // 滑标
                isIndicatorInit: false, // 初始化状态
                indicatorLeft: 0, // 位置left
                indicatorTop: 0, // 位置top
                indicatorWithAnimation: false, // 是否需要动画

                // 滚动结束的监听器
                scrollEndListener: null,

                // 更新
                isUpdating: false, // 是否正在更新
                needWaitingCalc: false, // 是否需要等待计算

                // 是否开启调试
                debug: false,
            };
        },
        computed: {
            // classes
            classes() {
                const classes = [];

                // 滚动方向
                if (!this.vertical) classes.push("pure-tabs-horizontal");
                else classes.push("pure-tabs-vertical");

                // 禁止滚动
                if (this.disabled) classes.push("pure-tabs-disabled-scroll");

                // 指示条初始化状态
                if (this.isIndicatorInit) classes.push("pure-tabs-indicator-initialization");

                // 指示器动画
                if (this.indicatorWithAnimation) classes.push("pure-tabs-indicator-with-animation");

                return classes;
            },

            // styles
            styles() {
                const styles = [];

                // 滑标的位置
                styles.push(`--pure-tabs-indicator-left: ${this.indicatorLeft}px`);
                styles.push(`--pure-tabs-indicator-top: ${this.indicatorTop}px`);

                return styles;
            },

            // 是否可以横向滚动
            canScrollX() {
                return !this.vertical && !this.disabled;
            },

            // 是否可以纵向滚动
            canScrollY() {
                return this.vertical && !this.disabled;
            },
        },
        provide() {
            return {
                globalHoverClass: computed(() => this.hoverClass),
                globalDisabledClass: computed(() => this.disabledClass),
                activatedValue: computed(() => this.value),
                addItem: this.addItem,
                updateItem: this.updateItem,
                removeItem: this.removeItem,
                emitTabClickFn: this.emitTabClickFn,
            };
        },
        expose: ["init"],
        watch: {
            // 监听激活项值的变化
            value: {
                async handler() {
                    // 重新初始化
                    this.init();
                },
            },

            // 监听指示器位置变化
            indicatorLeft: {
                handler() {
                    this.indicatorWithAnimation = true;
                },
            },

            // 监听指示器位置变化
            indicatorTop: {
                handler() {
                    this.indicatorWithAnimation = true;
                },
            },
        },
        created() {
            // 初始化IDs
            this.initIDs();
        },
        methods: {
            // 初始化IDs
            initIDs() {
                this.scrollViewId = `PureTabsScrollView_${Date.now()}_${this.randomString()}`;
                this.containerId = `PureTabsItems_${Date.now()}_${this.randomString()}`;
            },

            // 添加 item
            addItem(id, data) {
                this.items[id] = data;
                // 重新初始化
                this.init();
            },

            // 更新 item
            async updateItem(id, data) {
                this.items[id] = data;
                // 重新初始化
                this.init();
            },

            // 移除 item
            async removeItem(id) {
                delete this.items[id];
                // 重新初始化
                this.init();
            },

            // 初始化
            async init() {
                // 正在更新
                if (this.isUpdating) {
                    this.needWaitingCalc = true;
                    return false;
                }

                // 更新状态
                this.isUpdating = true;

                // 等待一下数据更新
                await this.$nextTick();
                await this.sleep(50);

                // 获取当前激活项的 item 数据
                const item = this.getActivatedItemData();

                // 未找到激活项
                if (!item) {
                    // 隐藏指示器
                    this.isIndicatorInit = false;
                    // 更新状态
                    this.isUpdating = false;
                    // 判断是否需要更新计算
                    this.isNeedUpdateCalc();
                    return false;
                }

                // 激活 item
                this.activatedItem(item);
            },

            // 判断是否需要更新计算
            async isNeedUpdateCalc() {
                // 判断是否需要更新
                if (this.needWaitingCalc) {
                    this.needWaitingCalc = false;
                    this.init();
                }
            },

            // 激活 item
            async activatedItem(item) {
                // 获取用于计算的节点信息
                // 每次都获取最新的，保证数据的准确性
                const svRect = await this.getScrollViewRect();
                const containerRect = await this.getContainerRect();
                const itemRect = await item.getRect(item);

                // 未成功获取到节点信息
                if (!svRect || !containerRect || !itemRect) {
                    // 隐藏指示器
                    this.isIndicatorInit = false;
                    // 更新状态
                    this.isUpdating = false;
                    // 判断是否需要更新计算
                    this.isNeedUpdateCalc();
                    return false;
                }

                // 设置指示条位置
                this.setIndicatorPosition(containerRect, itemRect);

                // 贴边吸附模式
                if (this.edge) this.toEdge(item, containerRect, itemRect);
                // 只滚动到可见模式
                else if (this.visible) this.toVisible(svRect, containerRect, itemRect);
                // 将激活项滚动到中间模式
                else this.toCenter(svRect, containerRect, itemRect);

                // 更新状态
                this.isUpdating = false;
                // 判断是否需要更新计算
                this.isNeedUpdateCalc();
            },

            // 获取当前激活项的 item 数据
            getActivatedItemData() {
                let item = null;
                for (let key in this.items) {
                    const _item = this.items[key];
                    if (_item?.value === this.value) {
                        item = _item;
                        break;
                    }
                }
                return item;
            },

            // 滚动到中间
            async toCenter(svRect, containerRect, itemRect) {
                // 用于计算的数据
                const svWidth = svRect.width;
                const svHeight = svRect.height;
                const containerLeft = containerRect.left;
                const containerTop = containerRect.top;
                const itemLeft = itemRect.left;
                const itemTop = itemRect.top;
                const itemWidth = itemRect.width;
                const itemHeight = itemRect.height;

                // 横向布局
                if (!this.vertical) {
                    this.scrollLeft = itemLeft - containerLeft - svWidth / 2 + itemWidth / 2;
                }

                // 纵向布局
                if (this.vertical) {
                    this.scrollTop = itemTop - containerTop - svHeight / 2 + itemHeight / 2;
                }

                // 更新指示器状态
                this.isIndicatorInit = true;
            },

            // 贴边吸附
            toEdge(item, containerRect, itemRect) {
                // #ifndef MP
                // 点击过快时会出现偏差，改为统一使用下方小程序兼容方式的实现，手动滚动
                // this.scrollIntoView = item.id;
                // // 更新指示器状态
                // this.isIndicatorInit = true;
                // #endif

                // 小程序无法滚动到子组件的id上去
                // 用函数实现
                // #ifdef MP
                // this.toEdgeByMP(containerRect, itemRect);
                // #endif

                this.toEdgeByMP(containerRect, itemRect);
            },

            // 贴边吸附
            async toEdgeByMP(containerRect, itemRect) {
                // 用于计算的数据
                const containerLeft = containerRect.left;
                const containerTop = containerRect.top;
                const itemLeft = itemRect.left;
                const itemTop = itemRect.top;

                // 横向布局
                if (!this.vertical) {
                    // 直接吸附到左边
                    const toLeft = itemLeft - containerLeft;

                    // 如果新旧值相同不会触发滚动,需处理一下
                    if (this.scrollLeft === toLeft) {
                        this.scrollLeft = toLeft + 1;
                        await this.sleep(10);
                        this.scrollLeft = toLeft;
                    } else {
                        this.scrollLeft = toLeft;
                    }

                    // 更新指示器状态
                    this.isIndicatorInit = true;
                }

                // 纵向布局
                if (this.vertical) {
                    // 直接吸附到顶部
                    const toTop = itemTop - containerTop;

                    // 如果新旧值相同不会触发滚动,需处理一下
                    if (this.scrollTop === toTop) {
                        this.scrollTop = toTop + 1;
                        await this.sleep();
                        this.scrollTop = toTop;
                    } else {
                        this.scrollTop = toTop;
                    }

                    // 更新指示器状态
                    this.isIndicatorInit = true;
                }
            },

            // 设置指示条位置
            setIndicatorPosition(containerRect, itemRect) {
                // 用于计算的数据
                const containerLeft = containerRect.left;
                const containerTop = containerRect.top;
                const itemLeft = itemRect.left;
                const itemTop = itemRect.top;
                const itemWidth = itemRect.width;
                const itemHeight = itemRect.height;

                // 横向布局
                if (!this.vertical) {
                    this.indicatorLeft = itemLeft - containerLeft + itemWidth / 2;
                    this.indicatorTop = itemHeight + itemTop - containerTop;
                }

                // 纵向布局
                if (this.vertical) {
                    this.indicatorLeft = 0;
                    this.indicatorTop = itemTop - containerTop + itemHeight / 2;
                }
            },

            // 只滚动到可见
            async toVisible(svRect, containerRect, itemRect) {
                // 用于计算的数据
                const svLeft = svRect.left;
                const svRight = svRect.right;
                const svTop = svRect.top;
                const svBottom = svRect.bottom;
                const svWidth = svRect.width;
                const svHeight = svRect.height;
                const containerLeft = containerRect.left;
                const containerTop = containerRect.top;
                const itemLeft = itemRect.left;
                const itemTop = itemRect.top;
                const itemRight = itemRect.right;
                const itemBottom = itemRect.bottom;
                const itemWidth = itemRect.width;
                const itemHeight = itemRect.height;

                // 横向布局
                if (!this.vertical) {
                    // 目标left
                    let toLeft = null;

                    // 先判断右侧是否超出
                    if (itemRight > svRight) {
                        toLeft = itemLeft - containerLeft - svWidth + itemWidth;
                    }

                    // 再判断左侧是否超出
                    else if (itemLeft < svLeft) {
                        toLeft = itemLeft - containerLeft;
                    }

                    // 如果新旧值相同不会触发滚动,需处理一下
                    if (this.scrollLeft === toLeft && toLeft !== null) {
                        this.scrollLeft = toLeft + 1;
                        await this.sleep();
                        this.scrollLeft = toLeft;
                    } else if (toLeft !== null) {
                        this.scrollLeft = toLeft;
                    }

                    // 更新指示器状态
                    this.isIndicatorInit = true;
                }

                // 纵向布局
                if (this.vertical) {
                    // 目标top
                    let toTop = null;

                    // 先判断底部是否超出
                    if (itemBottom > svBottom) {
                        toTop = itemTop - containerTop - svHeight + itemHeight;
                    }

                    // 再判断顶部是否超出
                    else if (itemTop < svTop) {
                        toTop = itemTop - containerTop;
                    }

                    // 如果新旧值相同不会触发滚动,需处理一下
                    if (this.scrollTop === toTop && toTop !== null) {
                        this.scrollTop = toTop + 1;
                        await this.sleep();
                        this.scrollTop = toTop;
                    } else if (toTop !== null) {
                        this.scrollTop = toTop;
                    }

                    // 更新指示器状态
                    this.isIndicatorInit = true;
                }
            },

            // 提交 tabClick 事件的函数
            emitTabClickFn(item) {
                this.$emit("tabClick", item);
            },

            // 获取 scroll-view 节点信息
            getScrollViewRect() {
                return new Promise(async (resolve) => {
                    uni.createSelectorQuery()
                        .in(this)
                        .select(`#${this.scrollViewId}`)
                        .boundingClientRect((rect) => {
                            resolve(rect);
                        })
                        .exec();
                });
            },

            // 获取父容器节点信息
            getContainerRect() {
                return new Promise(async (resolve) => {
                    uni.createSelectorQuery()
                        .in(this)
                        .select(`#${this.containerId}`)
                        .boundingClientRect((rect) => {
                            resolve(rect);
                        })
                        .exec();
                });
            },

            // 指示器动画结束事件
            handleIndicatorAnimationEnd() {
                this.indicatorWithAnimation = false;
            },

            // 生成一个36位的随机字符串
            randomString() {
                return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
            },

            // Sleep
            sleep(time = 10) {
                return new Promise((resolve) => {
                    let timer = setTimeout(() => {
                        clearTimeout(timer);
                        resolve();
                    }, time);
                });
            },
        },
    };
</script>

<style>
    /* 隐藏滚动条 */
    ::-webkit-scrollbar {
        width: 0;
        height: 0;
    }

    ::-webkit-scrollbar-thumb {
        width: 0;
        height: 0;
    }
</style>

<style scoped>
    @import "./style.css";
</style>
